/**
 * Copyright (C) 2008 Christopher Allen Ogden
 *
 *     This file is part of Odysi.
 *
 *  Odysi is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Odysi is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Odysi.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "luaAux.h"
#include <boost/shared_array.hpp>
#include <stdexcept>
#include <boost/lexical_cast.hpp>
#include "log.h"
#include "packetHeaders.h"
#include "config.h"

namespace Lua {
    void registerPointer(lua_State *L, const std::string &name, void *p)
    {
        lua_pushstring(L, name.c_str());
        lua_pushlightuserdata(L, p);
        lua_settable(L, LUA_REGISTRYINDEX);
    }

    void *getRegisteredPointer(lua_State *L, const std::string &name)
    {
        lua_pushstring(L, name.c_str());
        lua_gettable(L, LUA_REGISTRYINDEX);
        return lua_touserdata(L, -1);
    }

    boost::shared_ptr<Packet> createRemoteCall(lua_State *L)
    {
        int n = lua_gettop(L);
        size_t size = (n - 1) * sizeof(int) + sizeof(int) + 1 + sizeof(size_t);

        // Get the size of the data
        for (int i = 2; i <= n; i++) {
            switch (lua_type(L, i)) {
                case LUA_TNUMBER:
                    size += sizeof(lua_Number);
                    break;
                case LUA_TSTRING:
                    size_t len;
                    lua_tolstring(L, i, &len);
                    size += len + sizeof(size_t);
                    break;
                case LUA_TBOOLEAN:
                    size += sizeof(int);
                    break;
                case LUA_TNIL:
                    size += 0;
                    break;
            }
        }

        // Serialize data
        boost::shared_array<unsigned char> data(new unsigned char[size]);
        unsigned char pktType = PH_LUA_REMOTE_CALL;
        memcpy(data.get(), &pktType, sizeof(unsigned char));
        int cmd = lua_tonumber(L, 1);
        memcpy(reinterpret_cast<unsigned char *>(data.get()) + 1, &cmd, sizeof(int));
        memcpy(reinterpret_cast<unsigned char *>(data.get()) + 1 + sizeof(int), &size, sizeof(size_t));

        int offset = sizeof(int) + 1 + sizeof(size_t);

        for (int i = 2; i <= n; i++) {
            int type = lua_type(L, i);
            memcpy(data.get() + offset, &type, sizeof(int));
            offset += sizeof(int);
            lua_Number num;
            size_t len;
            int value;

            switch (type) {
                case LUA_TNUMBER:
                    num = lua_tonumber(L, i);
                    memcpy(data.get() + offset, &num, sizeof(lua_Number));
                    offset += sizeof(lua_Number);
                    break;
                case LUA_TSTRING:
                    lua_tolstring(L, i, &len);
                    memcpy(data.get() + offset, &len, sizeof(size_t));
                    offset += sizeof(size_t);
                    memcpy(data.get() + offset, lua_tolstring(L, i, &len), len);
                    offset += len;
                    break;
                case LUA_TBOOLEAN:
                    value = lua_toboolean(L, i);
                    memcpy(data.get() + offset, &value, sizeof(int));
                    offset += sizeof(int);
                    break;
                case LUA_TNIL:
                    offset += 0;
                    break;
                case LUA_TTHREAD:
                    throw std::runtime_error(std::string("Lua::processRemoteCall: invalid parameter type: thread"));
                case LUA_TTABLE:
                    throw std::runtime_error(std::string("Lua::processRemoteCall: invalid parameter type: table"));
                case LUA_TFUNCTION:
                    throw std::runtime_error(std::string("Lua::processRemoteCall: invalid parameter type: function"));
                default:
                    throw std::runtime_error(std::string("Lua::processRemoteCall: invalid parameter type: ") + boost::lexical_cast<std::string>(type));
            }
        }

        assert(offset == size);

        // Return the packet
        return boost::shared_ptr<Packet>(new Packet(data.get(), size));
    }

    void processRemoteCall(lua_State *L, const Packet &pkt, int conn)
    {
        if (pkt.getLength() < sizeof(int) + 1 + sizeof(size_t)) {
            Log::GS().error("Lua::processRemoteCall: Invalid packet length.");
            return;
        }

        unsigned char pktType;
        memcpy(&pktType, pkt.getData(), sizeof(unsigned char));

        if (pktType != PH_LUA_REMOTE_CALL)
        {
            Log::GS().error("Lua::processRemoteCall: Invalid packet type.");
            return;
        }

        int cmd;
        memcpy(&cmd, reinterpret_cast<const unsigned char *>(pkt.getData()) + 1, sizeof(int));

        lua_getglobal(L, "remoteCall");
        if (!lua_istable(L, -1)) {
            Log::GS().error("Lua::processRemoteCall: remoteCall table not found.");
            return;
        }

        lua_pushnumber(L, cmd);
        lua_gettable(L, -2);
        if (!lua_isfunction(L, -1)) {
            Log::GS().error("Lua::processRemoteCall: remoteCall function not found.");
            return;
        }

        if (conn >= 0) lua_pushnumber(L, conn);

        int n = 0;

        const unsigned char *offset = reinterpret_cast<const unsigned char *>(pkt.getData()) + sizeof(int) + 1 + sizeof(size_t);
        while (offset < reinterpret_cast<const unsigned char *>(pkt.getData()) + pkt.getLength()) {
            int type;
            memcpy(&type, offset, sizeof(int));
            offset += sizeof(int);
            int value;
            size_t len;
            lua_Number num;
            n++;

            switch (type) {
                case LUA_TNUMBER:
                    memcpy(&num, offset, sizeof(lua_Number));
                    lua_pushnumber(L, num);
                    offset += sizeof(lua_Number);
                    break;
                case LUA_TSTRING:
                    memcpy(&len, offset, sizeof(size_t));
                    offset += sizeof(size_t);
                    lua_pushlstring(L, reinterpret_cast<const char *>(offset), len);
                    offset += len;
                    break;
                case LUA_TBOOLEAN:
                    memcpy(&value, offset, sizeof(int));
                    lua_pushboolean(L, value);
                    offset += sizeof(int);
                    break;
                case LUA_TNIL:
                    offset += 0;
                    lua_pushnil(L);
                    break;
                case LUA_TTHREAD:
                    Log::GS().error(std::string("Lua::processRemoteCall: invalid parameter type: thread"));
                    break;
                case LUA_TTABLE:
                    Log::GS().error(std::string("Lua::processRemoteCall: invalid parameter type: table"));
                    break;
                case LUA_TFUNCTION:
                    Log::GS().error(std::string("Lua::processRemoteCall: invalid parameter type: function"));
                    break;
                default:
                    Log::GS().error(std::string("Lua::processRemoteCall: invalid parameter type: ") + boost::lexical_cast<std::string>(type));
                    return;
            }
        }

        lua_pcall(L, n + (conn >= 0 ? 1 : 0), 0, 0);
    }

    void setPath(lua_State *L)
    {
        lua_getglobal(L, "package");
        lua_pushstring(L, "path");
        lua_pushstring(L, PKGDATADIR SCRIPT_DIR "?.lua");
        lua_settable(L, -3);
    }
}
